/**
 * @作者 faner
 * @创建时间 2020/12/31 12:04
 */
package com.faner.fast.csv.utils;

import com.faner.fast.csv.annotation.ResponseCsv;
import com.faner.fast.csv.converters.DefaultConvertible;
import com.faner.fast.csv.handler.ExcelHeader;
import com.faner.fast.exception.FailedException;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class CsvUtils {

    /**
     * 单例模式
     * 通过{@link CsvUtils#getInstance()}获取对象实例
     */
    private static volatile CsvUtils csvUtils;

    private CsvUtils() {
    }

    /**
     * 双检锁保证单例
     */
    public static CsvUtils getInstance() {
        if (null == csvUtils) {
            synchronized (CsvUtils.class) {
                if (null == csvUtils) {
                    csvUtils = new CsvUtils();
                }
            }
        }
        return csvUtils;
    }


    /*---------------------------------------7.基于注解的CSV读取--------------------------------------------------*/
    /*  一. 操作流程 ：                                                                                           */
    /*      1) 读取表头信息,与给出的Class类注解匹配                                                                 */
    /*      2) 读取表头下面的数据内容, 按行读取, 并映射至java对象                                                     */
    /*  二. 参数说明                                                                                              */
    /*      *) path             =>      待读取文件路径                                                            */
    /*      *) is               =>      待读取文件流                                                              */
    /*      *) clazz            =>      映射对象                                                                  */

    /**
     * 基于注解读取CSV文件
     *
     * @param path  待读取文件路径
     * @param clazz 待绑定的类(绑定属性注解{@link ResponseCsv})
     * @return 返回转换为设置绑定的java对象集合
     * @throws FailedException exception
     */

    public <T> List<T> readCSV2Objects(String path, Class<T> clazz) {

        try (InputStream is = new FileInputStream(new File(path))) {
            return readCSVByMapHandler(is, clazz);
        } catch (IOException | FailedException e) {
            throw new FailedException("read [" + path + "] CSV Error: "+e);
        }
    }

    /**
     * 基于注解读取CSV文件
     *
     * @param is    待读取文件输入流
     * @param clazz 待绑定的类(绑定属性注解{@link ResponseCsv})
     * @return 返回转换为设置绑定的java对象集合
     * @throws FailedException exception
     */
    public <T> List<T> readCSV2Objects(InputStream is, Class<T> clazz) {

        try {
            return readCSVByMapHandler(is, clazz);
        } catch (FailedException | IOException e) {
            throw new FailedException("read CSV Error: "+ e);
        }
    }

    // 读取csv
    private <T> List<T> readCSVByMapHandler(InputStream is, Class<T> clazz)
            throws IOException, FailedException {

        List<T> records = new ArrayList<>();

        List<ExcelHeader> headers = Utils.getHeaderList(clazz);
        if (null == headers || headers.size() <= 0) {
            throw new FailedException("[" + clazz + "] must configuration @ExcelFiled");
        }
        String[] csvHeaders = new String[headers.size()];
        for (int i = 0; i < headers.size(); i++) {
            csvHeaders[i] = headers.get(i).getTitle();
        }
        CSVFormat format = CSVFormat.EXCEL.withHeader(csvHeaders).withSkipHeaderRecord(true);
        try (Reader read = new InputStreamReader(is, StandardCharsets.UTF_8);
             CSVParser parser = new CSVParser(read, format)) {
            for (CSVRecord _parser : parser) {
                T obj;
                try {
                    obj = clazz.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    throw new FailedException(String.valueOf(e));
                }
                for (ExcelHeader header : headers) {
                    String value = _parser.get(header.getTitle());
                    Object objectVal;
                    String filed = header.getFiled();
                    // 读取转换器
                    if (null != header.getReadConverter() &&
                            header.getReadConverter().getClass() != DefaultConvertible.class) {
                        objectVal = header.getReadConverter().execRead(value);
                    } else {
                        // 默认转换
                        objectVal = Utils.str2TargetClass(value, header.getFiledClazz());
                    }
                    Utils.copyProperty(obj, filed, objectVal);
                }
                records.add(obj);
            }
        }
        return records;
    }

    /*---------------------------------------8.基于注解的CSV导出--------------------------------------------------*/
    /*  一. 操作流程 ：                                                                                           */
    /*      1) 写入表头内容(可选)                                                                                  */
    /*      2) 写入数据内容                                                                                       */
    /*  二. 参数说明                                                                                              */
    /*      *) data             =>      导出内容List集合                                                          */
    /*      *) isWriteHeader    =>      是否写表头                                                                */
    /*      *) path             =>      导出文件路径                                                              */
    /*      *) os               =>      导出文件流                                                                */
    /*      *) clazz            =>      映射对象                                                                  */

    /**
     * 基于注解导出CSV文件
     *
     * @param data  待导出
     * @param clazz {@link ResponseCsv}映射对象Class
     * @param path  导出文件路径
     * @throws FailedException exception
     */
    public void exportObjects2CSV(List<?> data, Class clazz, String path) throws FailedException {

        try {
            Writer writer = new FileWriter(path);
            exportCSVByMapHandler(data, clazz, true, writer);
        } catch (FailedException | IOException e) {
            throw new FailedException(String.valueOf(e));
        }
    }

    /**
     * 基于注解导出CSV文件流
     *
     * @param data  待导出
     * @param clazz {@link ResponseCsv}映射对象Class
     * @param os    输出流
     * @throws FailedException exception
     */
    public void exportObjects2CSV(List<?> data, Class clazz, OutputStream os)
            throws FailedException {

        try {
            Writer writer = new OutputStreamWriter(os);
            exportCSVByMapHandler(data, clazz, true, writer);
        } catch (FailedException | IOException e) {
            throw new FailedException(String.valueOf(e));
        }
    }

    /**
     * 基于注解导出CSV文件
     *
     * @param data          待导出
     * @param clazz         {@link ResponseCsv}映射对象Class
     * @param isWriteHeader 是否写入文件
     * @param path          导出文件路径
     * @throws FailedException exception
     */
    public void exportObjects2CSV(List<?> data, Class clazz, boolean isWriteHeader, String path)
            throws FailedException {

        try {
            Writer writer = new FileWriter(path);
            exportCSVByMapHandler(data, clazz, isWriteHeader, writer);
        } catch (FailedException | IOException e) {
            throw new FailedException(String.valueOf(e));
        }
    }

    /**
     * 基于注解导出CSV文件流
     *
     * @param data          待导出
     * @param clazz         {@link ResponseCsv}映射对象Class
     * @param isWriteHeader 是否写入文件
     * @param os            输出流
     * @throws FailedException exception
     */
    public void exportObjects2CSV(List<?> data, Class clazz, boolean isWriteHeader, OutputStream os)
            throws FailedException {

        try {
            Writer writer = new OutputStreamWriter(os);
            exportCSVByMapHandler(data, clazz, isWriteHeader, writer);
        } catch (FailedException | IOException e) {
            throw new FailedException(String.valueOf(e));
        }
    }

    private static final byte[] UTF_8_DOM = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};

    // 生成CSV
    private void exportCSVByMapHandler(List<?> data, Class clazz, boolean isWriteHeader, Appendable appendable)
            throws FailedException, IOException {

        List<ExcelHeader> headers = Utils.getHeaderList(clazz);
        appendable.append(new String(UTF_8_DOM, StandardCharsets.UTF_8));

        try (CSVPrinter printer = new CSVPrinter(appendable, CSVFormat.EXCEL)) {

            if (isWriteHeader) {
                for (ExcelHeader header : headers) {
                    printer.print(header.getTitle());
                }
                printer.println();
            }
            // 写数据
            for (Object _data : data) {
                for (ExcelHeader header : headers) {
                    printer.print(
                            Utils.getProperty(_data, header.getFiled(), header.getWriteConverter())
                    );
                }
                printer.println();
            }
            printer.flush();
        }
    }
}
